﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Harkon.AppServer.cOne.Service;
using System.Threading;
using log4net;
using Harkon.AppServer.cOne.Events;
using log4net.Core;
using Harkon.AppServer.cOne.Registry;
using System.ServiceModel;
using Harkon.AppServer.cOne.Service.Remote;

namespace Harkon.AppServer.cOne.Cluster
{
    public interface IParticipantManager : IService
    {

    }

    public class ParticipantManager : AbstractService, IParticipantManager, IMulticastMessageInterested
    {
        private static ILog logger = LogManager.GetLogger(typeof(ParticipantManager));

        public IMulticastSender Sender { get; set; }
        public IMulticastReceiver Receiver { get; set; }
        public ProtocolHelper Helper { get; set; }

        private List<string> registeredParticipants = new List<string>();
        private List<MulticastMessage> previouslyReceivedMessages = new List<MulticastMessage>();

        public IEventManager EventManager { get; set; }

        #region ILifecycleAware

        public override bool Init()
        {
            if (base.Init())
            {
                EventConsumerDelegate newParticipantDelegate = new EventConsumerDelegate(ClusterParticipantEntered);
                EventManager.Subscribe(InfrastructureTopics.NEW_CLUSTER_PARTICIPATE, newParticipantDelegate);
                EventConsumerDelegate participantDepartedDelegate = new EventConsumerDelegate(ClusterParticipantDeparted);
                EventManager.Subscribe(InfrastructureTopics.DEPARTED_CLUSTER_PARTICIPATE, participantDepartedDelegate);
                EventConsumerDelegate multicastCheckDone = new EventConsumerDelegate(MulticastCheckDone);
                EventManager.Subscribe(InfrastructureTopics.MULTICAST_CHECK_DONE, multicastCheckDone);

                worker = new Thread(Run);
                worker.Name = "ClusterWatcher";
                worker.IsBackground = true;
                return true;
            }
            return false;
        }

        public override bool Start()
        {
            if (base.Start())
            {
                Receiver.AddMulticastMessageConsumer(this);
                running = true;
                worker.Start();
                return true;
            }
            return false;
        }

        public override bool Stop()
        {
            if (base.Stop())
            {
                running = false;
                lock (this)
                {
                    Monitor.Pulse(this);
                }
                return true;
            }
            return false;
        }

        #endregion

        #region Multicast message arrived members

        public void MulticastMessageArrived(MulticastMessage incomingMessage)
        {
            lock (previouslyReceivedMessages)
            {
                previouslyReceivedMessages.Add(incomingMessage);
            }
        }

        #endregion

        #region Cluster deployment watching

        private Thread worker = null;
        private bool running = false;
        private TimeSpan waitCycle = new TimeSpan(0, 0, 1);

        private void Run()
        {
            while (running)
            {
                lock (this)
                {
                    Monitor.Wait(this, waitCycle);
                }

                //Start work
                //Check if there is any registered participant
                Trace(string.Format("Checking received messages from {0} participant{1}",
                        previouslyReceivedMessages.Count,
                        previouslyReceivedMessages.Count == 1 ? "" : "s"));
                //Fire multicast update done
                EventManager.Publish(InfrastructureTopics.MULTICAST_CHECK_DONE, string.Empty);

                //okay, check the previously received messages and update registered participants list
                lock (previouslyReceivedMessages)
                {
                    //Convert messages to string list
                    List<string> tmpParticipantsList = new List<string>(
                        previouslyReceivedMessages.Select<MulticastMessage, string>(
                            message => message.ServerId)
                    );
                    //remove from string list the own server id
                    tmpParticipantsList.Remove(Helper.ServerId);

                    //Create a intersection of tmp list and already registered ones
                    var intersection = registeredParticipants.Intersect<string>(tmpParticipantsList);

                    //Except constructions
                    var newParticipants = tmpParticipantsList.Except<string>(intersection);
                    var leftParticipants = new List<string>(registeredParticipants.Except<string>(intersection));

                    //insert new ones and fire "new partner" event
                    foreach (var newParticipant in newParticipants)
                    {
                        logger.DebugFormat("New participant {0} -> register", newParticipant);
                        lock (registeredParticipants)
                        {
                            registeredParticipants.Add(newParticipant);
                            EventManager.Publish(InfrastructureTopics.NEW_CLUSTER_PARTICIPATE, newParticipant);
                        }
                    }
                    //remove left ones and fire "departed partner" event
                    foreach (var leftParticipant in leftParticipants)
                    {
                        logger.DebugFormat("Departed participant {0} -> unregister", leftParticipant);
                        registeredParticipants.Remove(leftParticipant);
                        EventManager.Publish(InfrastructureTopics.DEPARTED_CLUSTER_PARTICIPATE, leftParticipant);
                    }
                }

                lock (previouslyReceivedMessages)
                {
                    //finally we clear the received message list
                    previouslyReceivedMessages.Clear();
                }

                //Say Hello to everybody
                MulticastMessage helloAgainMessage = Helper.CreateMessage(MessageType.Hello);
                Sender.SendMessage(helloAgainMessage);
            }
        }

        #endregion

        #region private helpers

        private void Trace(string message)
        {
            Level TRACE = logger.Logger.Repository.LevelMap["TRACE"];
            logger.Logger.Log(typeof(ParticipantManager), TRACE, message, null);
        }

        #endregion

        #region Event delegate methods

        public void ClusterParticipantEntered(string participantId)
        {
            logger.DebugFormat("{0} entered", participantId);
            AddNewRegistryChannel(participantId);
        }

        public void ClusterParticipantDeparted(string participantId)
        {
            logger.DebugFormat("{0} left", participantId);
            RemoveRegistryChannel(participantId);
        }

        public void MulticastCheckDone(string empty)
        {
            logger.Debug("Multicast check done...");
            RegisterRemoteComponents();
        }

        #endregion

        #region Remote connection to foreign registry

        private IDictionary<string, IRegistry> foreignRegistryChannels = new Dictionary<string, IRegistry>();

        private void RegisterRemoteComponents()
        {
            //TODO: RemoteComponentInformation!
            foreach (var foreignRegistryEntry in foreignRegistryChannels)
            {
                string uri = foreignRegistryEntry.Key;
                IRegistry foreignRegistry = foreignRegistryEntry.Value;
                string remoteUri = (foreignRegistry as IClientChannel).RemoteAddress.Uri.ToString();
                logger.DebugFormat("Check registered components in remote registry under address: {0}",
                    remoteUri);
                try
                {
                    ComponentInformation[] foreignComponents = foreignRegistry.GetRegisteredComponents();
                    logger.DebugFormat("Registering {0} remote components in local registry", foreignComponents.Length);
                    foreach (var remoteComponent in foreignComponents)
                    {
                        if (ServiceRegistry.IsComponentRegistered(remoteComponent))
                            continue;
                        ServiceName[] services = foreignRegistry.GetRegisteredServicesForComponent(remoteComponent);
                        logger.DebugFormat("Registering remote component {0}", remoteComponent.ToString());
                        RemoteComponentInformation remoteComponetInformation = new RemoteComponentInformation(remoteComponent);
                        string serviceRemoteUri = remoteUri.Replace("Registry", "RemoteCall");
                        foreach (var remoteServiceName in services)
                        {
                            RemoteServiceProxy remoteProxy 
                                = new RemoteServiceProxy(remoteServiceName) { RemoteServiceUri = serviceRemoteUri };
                            remoteComponetInformation.AddService(remoteServiceName, remoteProxy);
                        }
                        ServiceRegistry.RegisterComponent(remoteComponetInformation);
                    }
                }
                catch (Exception ex)
                {
                    logger.ErrorFormat(ex.Message, ex);
                }
            }
        }

        private void AddNewRegistryChannel(string foreignRegistryUri)
        {
            BasicHttpBinding binding = new BasicHttpBinding();
            EndpointAddress address = new EndpointAddress(foreignRegistryUri);
            IRegistry foreignRegistry = ChannelFactory<IRegistry>.CreateChannel(binding, address);
            logger.InfoFormat("Registering channel to foreign registry with address: {0}", address.Uri);
            foreignRegistryChannels.Add(foreignRegistryUri, foreignRegistry);
        }

        private void RemoveRegistryChannel(string foreignRegistryUri)
        {
            IClientChannel registryClient = foreignRegistryChannels[foreignRegistryUri] as IClientChannel;
            registryClient.Dispose();
            logger.InfoFormat("Unregistering channel to foreign registry with address: {0}", registryClient.RemoteAddress.Uri);
            foreignRegistryChannels.Remove(foreignRegistryUri);
        }

        #endregion
    }
}
